--[[
Name: targetAssist
Author: Sonora (The Dragonflight, US Kirin Tor)
Website & SVN: http://code.google.com/p/browniehelper/source/browse/#svn/trunk/targetAssist
License: GNU General Public License v3
]]

--Utilities
local function deepcopy(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for index, value in pairs(object) do
            new_table[_copy(index)] = _copy(value)
        end
        return setmetatable(new_table, _copy(getmetatable(object)))
    end
    return _copy(object)
end

local function hasValue(t, value)
    for _, v in ipairs(t) do
        if v == value then return true end
    end
    for k, v in pairs(t) do
        if v == value then return true end
    end
end

--Ace3 addon application object.
local AceConfig = LibStub("AceConfig-3.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")
targetAssist = LibStub("AceAddon-3.0"):NewAddon("targetAssist", "AceConsole-3.0", "AceComm-3.0", "AceSerializer-3.0", "AceEvent-3.0", "AceTimer-3.0")

function targetAssist:OnInitialize()
    
    --Addon wide data structures
    self.roster = {} --{unitName=unitID,}
    self.mainassists = {}
    self.tanks = {}
    
    self.buttonWidth = 0
    self.postCombatCalls = {}
    self.previousBroadcasts = {} --{{fromPlayer, channel, targets},}
    
    --Setup a default profile table for the database
    local defaultProfileDb = {
        profile = {
            config = deepcopy(self.defaultConfig)
        }
    }
    
    --Setup data base communication    
    self.db = LibStub("AceDB-3.0"):New("targetAssistDb", defaultProfileDb)
    self.db.RegisterCallback(self, "OnProfileChanged", "OnProfileChanged")
    
    --Register us as recievers of our AceComm messages
    self:RegisterComm('targetAssist.broadcastTargets', 'targetReceiver')
    
    --If SharedMedia is loaded, share our media
    if IsAddOnLoaded("SharedMedia") then
        local lib = LibStub("LibSharedMedia-3.0")
        lib:Register(lib.MediaType.STATUSBAR, "targetAssist.Bar", [=[Interface\Addons\targetAssist\media\barTex]=])
    end

    --This call in turn handles changes to targetAssist.config and config GUI construction
    self:OnProfileChanged(nil, self.db, self.db:GetCurrentProfile())
    
    --Setup an update timer
    self:ScheduleRepeatingTimer('onUpdate', self.config.updateInterval)
    
    --Event handlers
    self:RegisterEvent('PARTY_MEMBERS_CHANGED')
    self:RegisterEvent('RAID_ROSTER_UPDATE')
    
    --Setup the user interface, and fire events which don't on their own at log in
    self:buildUI()
    self:updateRoster()
    
end

function targetAssist:buildUI()
    
    --Setup options panels
    local basePanel = self:createBasePanel()
    AceConfig:RegisterOptionsTable('targetAssist', basePanel)
    self.optionsBaseFrame = AceConfigDialog:AddToBlizOptions("targetAssist", "targetAssist")
    
    AceConfig:RegisterOptionsTable("targetAssist.Profiles", LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db))
    AceConfigDialog:AddToBlizOptions("targetAssist.Profiles", "Profiles", "targetAssist")
    
    --Build a context menu
    self.contextMenu = CreateFrame("Frame", "targetAssistContextMenu", UIParent, "UIDropDownMenuTemplate")
    
end

function targetAssist:targetReceiver(prefix, message, distribution, sender)

    if not UnitIsUnit('player',sender) then

        local success, newTargets = self:Deserialize(message)
    
        if #self.previousBroadcasts == self.config.broadcastHistory then
            table.remove(self.previousBroadcasts, 1)
        end
        table.insert(self.previousBroadcasts, {sender, distribution, newTargets})
    
        if self.config.autoAcceptBroadcasts then
            self.config.targets = newTargets
            self:updateConfig()
        else
            self:yesnoBox('A set of assist targets has been sent by '..sender..'.  Would you like to change to this new target list?', 
                function()
                    self.config.targets = newTargets
                    self:updateConfig()
                end
            )
        end
    
    end
end

function targetAssist:OnProfileChanged(eventName, db, newProfile)
    self.config = db.profile.config
    self:updateConfig(true)
    
end

function targetAssist:updateConfig(rebuildHeader)

    if InCombatLockdown() then
        self.updateConfigPostCombat = true
    else
        self.updateConfigPostCombat = false
        if rebuildHeader then self:createHeaderFrame() end
        self:createTargetButtons()
    end
    
end

local function playerInGroup()
    return GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0
end

function targetAssist:addTanks()

    table.sort(self.tanks)
    for i, tankName in ipairs(self.tanks) do
        if not hasValue(self.config.targets, tankName) then
            table.insert(self.config.targets, i, tankName)
        end
    end
    self:updateConfig()

end

function targetAssist:addMainAssists()
    
    table.sort(self.mainassists)
    for i, assistName in ipairs(self.mainassists) do
        if not hasValue(self.config.targets, assistName) then
            table.insert(self.config.targets, i, assistName)
        end
    end
    self:updateConfig()
    
end

function targetAssist:updateRoster()
    self.mainassists = {}
    self.tanks = {}
    
    if playerInGroup() then        
        for i=1,4 do
            local unitName = GetUnitName('party'..i)
            if unitName then 
                self.roster[unitName] = 'party'..i 
                local role = UnitGroupRolesAssigned('party'..i)
                if role == 'TANK' then table.insert(self.tanks, unitName) end
            end
        end
        for i=1,40 do
            local unitName = GetUnitName('raid'..i)
            if unitName then 
                self.roster[unitName] = 'raid'..i 
                local name, rank, subgroup, level, class, fileName, zone, online, isDead, role, isML = GetRaidRosterInfo(i)
                if role == 'maintank' then table.insert(self.tanks, unitName) end
                if role == 'mainassist' then table.insert(self.mainassists, unitName) end
            end
        end
        
        if self.config.autoUpdateTanks then self:addTanks() end
        if self.config.autoUpdateMainAssists then self:addMainAssists() end
        
        if self.headerFrame then 
            self:registerPostCombatCall(
                function() 
                    self.headerFrame:Show()
                    self:createTargetButtons()
                end
            )
        end
        
    else
        if self.headerFrame then 
            self:registerPostCombatCall(
                function() 
                    self.headerFrame:Hide()
                    for _, frame in pairs(self.targetButtons) do frame:Hide() end
                end
            ) 
        end
    end
    
    if self.contextMenu:IsShown() then self.contextMenu:Hide() end
end

local function getUnitHealth(unitID)
    --returns percentage
    return UnitHealth(unitID) / UnitHealthMax(unitID)
end

function targetAssist:registerPostCombatCall(call) table.insert(self.postCombatCalls, call) end
function targetAssist:onUpdate()

    if not InCombatLockdown() then
        if self.updateConfigPostCombat then self:updateConfig() end
    
        for _, call in ipairs(self.postCombatCalls) do call() end
        self.postCombatCalls = {}
    
    end

    for targetName, buttonFrame in pairs(self.targetButtons or {}) do
        local unitName = buttonFrame.unitID and GetUnitName(buttonFrame.unitID)
        if unitName then
            buttonFrame.fontString:SetText(unitName)
            if UnitIsUnit('playertarget', buttonFrame.unitID) then
                buttonFrame.texture:SetVertexColor(unpack(self.config.myTargetColor))
            else 
                buttonFrame.texture:SetVertexColor(unpack(self.config.targetColor))
            end
        
            if self.config.showHealth then
                local width = getUnitHealth(buttonFrame.unitID) * self.buttonWidth
                if width > 0 then
                    buttonFrame.texture:SetWidth( width )
                else
                    buttonFrame.texture:SetWidth( self.buttonWidth )
                    buttonFrame.texture:SetVertexColor(unpack(self.config.missingColor))
                end
            end
        
            if buttonFrame.raidIcon then
                local iconIdx = GetRaidTargetIndex(buttonFrame.unitID)
                if iconIdx then
                    buttonFrame.raidIcon:SetTexture(self.config.raidIcons[iconIdx][2])
                    buttonFrame.raidIcon:Show()
                else
                    buttonFrame.raidIcon:Hide()
                end
            end
        
        else
            buttonFrame.fontString:SetText('')
            buttonFrame.texture:SetVertexColor(unpack(self.config.missingColor))
            if buttonFrame.raidIcon then buttonFrame.raidIcon:Hide() end
        end
    end    
end

function targetAssist:PARTY_MEMBERS_CHANGED() self:updateRoster() end
function targetAssist:RAID_ROSTER_UPDATE() self:updateRoster() end